/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.zookeeper.client;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 在ConnectionStringParser解释器中会对服务器地址做一个简单的处理，
 * 并将服务器地址和相应的端口封装成一个InetSocketAddress对象，以ArrayList形式保存在ConnectStringParser.
 * serverAddresses集合中,然会返回处理后的地址信息会进一步通过StaticHostProvider类中;
 */
public final class StaticHostProvider implements HostProvider {
    private static final Logger LOG = LoggerFactory.getLogger(StaticHostProvider.class);

    private final List<InetSocketAddress> serverAddresses = new ArrayList<>(5);

    // 记录循环队列中当前正在连接的服务器地址的下标
    private int lastIndex = -1;

    // 记录循环队列中当前遍历到的下标
    private int currentIndex = -1;

    /**
     * 将ConnectStringParser.serverAddresses集合中哪些没有被解析的服务器地址再解析
     * 然后利用Collections.shuffle随即打乱
     * 
     * @param serverAddresses possibly unresolved ZooKeeper server addresses
     * @throws UnknownHostException
     * @throws IllegalArgumentException if serverAddresses is empty or resolves to an empty list
     */
    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) throws UnknownHostException {
        for (InetSocketAddress address : serverAddresses) {
            InetAddress resolvedAddresses[] = InetAddress.getAllByName(address.getHostName());
            // 针对ConnectStringParser.serverAddresses集合中哪些没有被解析的服务器地址
            // staticHostProvider首先会对这些地址逐个进行解析，然后再放入serverAddresses列表中去
            for (InetAddress resolvedAddress : resolvedAddresses) {
                this.serverAddresses.add(new InetSocketAddress(resolvedAddress.getHostAddress(), address.getPort()));
            }
        }

        if (this.serverAddresses.isEmpty()) {
            throw new IllegalArgumentException("A HostProvider may not be empty!");
        }
        // 打乱的意义是 避免单台zk服务器压力过大(next方法表示每次client只和一个zk server连接),接收了所有的请求
        // 打乱为什么放在客户端，不在server端?  如果放在server端打乱顺序，那么所有client端记录的server顺序还是一样的，没有起到压力均分的效果。
        // 随机打乱服务器地址顺序
        Collections.shuffle(this.serverAddresses);
    }

    /**
     * 返回当前服务器地址的个数
     * @return
     */
    @Override
    public int size() {
        return serverAddresses.size();
    }

    /**
     * 返回一个zk服务器地址让客户端进行连接
     *
     * 利用循环队列，并且在当前用的地址和上次用的地址一样时，等待一段时间(小技巧)
     *
     * @param spinDelay
     * @return
     */
    @Override
    public InetSocketAddress next(long spinDelay) {
        ++currentIndex;
        if (currentIndex == serverAddresses.size()) {
            // 循环队列的特性
            currentIndex = 0;
        }
        // 如果当前用的地址和上次用的地址一样，则等待spinDelay的时间
        if (currentIndex == lastIndex && spinDelay > 0) {
            try {
                Thread.sleep(spinDelay);
            } catch (InterruptedException e) {
                LOG.warn("Unexpected exception", e);
            }
        }
        // 第一次连接
        else if (lastIndex == -1) {
            lastIndex = 0;
        }
        // 注意serverAddresses是随机打乱过的服务器地址列表
        return serverAddresses.get(currentIndex);
    }

    /**
     * 回调方法，若client和server创建连接成功，则用这个方法通知hostProvider
     */
    @Override
    public void onConnected() {
        lastIndex = currentIndex;
    }
}
